package config;

import java.util.Properties;

import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * Database setup for all lab projects in this course.
 * <p>
 * Java Config equivalent of <code>datasource/db-config.xml</code.
 */
@Configuration
@EnableTransactionManagement
public class DbConfig {

	/**
	 * Allows a different script to be specified in different lab projects
	 */
	@Value("${data.load.script}")
	protected String dataLoadScript;

	/**
	 * Using @PropertySources <i>does not</i> automatically register a
	 * {@link PropertySourcesPlaceholderConfigurer}. There does not seem to be a
	 * Java Config equivalent to &lt;context:property-placeholder&gt;.
	 * 
	 * @return The configurer.
	 */
	@Bean
	public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
		return new PropertySourcesPlaceholderConfigurer();
	}

	/**
	 * Create and populate an in-memory HSQL database;
	 * 
	 * @return the HSQL data-source
	 */
	@Bean
	public DataSource dataSource() {
		return new EmbeddedDatabaseBuilder()
				.addScript("classpath:datasource/db-schema.sql")
				.addScript(dataLoadScript) //
				.build();

	}

	/**
	 * Enable exception translation. Convert JPA's {@link PersistenceException}
	 * to Spring's {@link DataAccessException}.
	 * 
	 * @return A Spring Bean post processor that causes an <i>after-throwing</i>
	 *         Aspect to be applied to any class annotated with Spring's
	 *         {@link Repository}.
	 */
	@Bean(name = "persistenceExceptionTranslationPostProcessor")
	public PersistenceExceptionTranslationPostProcessor petpp() {
		return new PersistenceExceptionTranslationPostProcessor();
	}

	/**
	 * The JPA transaction manager.
	 * 
	 * @param emf
	 *            The JPA EntityManager factory used by the transaction manager
	 *            to run transactions.
	 * @return A Spring transaction manager customized for JPA.
	 */
	@Bean
	public PlatformTransactionManager transactionManager(
			EntityManagerFactory emf) {
		return new JpaTransactionManager(emf);
	}

	/**
	 * A Spring {@link FactoryBean} that will be used to create the JPA
	 * EntityManager factory the application need to create EntityManagers. This
	 * is really an {@link EntityManagerFactory} factory.
	 * 
	 * @param packagesToScan
	 *            Where to look for @Entty classes - avoids the need for a
	 *            <code>persistence.xml</code> file.
	 * @param dataSource
	 *            The test-database.
	 * @return The factory-bean. Spring will automatically invoke its
	 *         {@link FactoryBean#getObject()} to get an EntityManagerFactory
	 *         instance.
	 */
	@Bean
	public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
			@Value("${entity.packages}") String packagesToScan,
			DataSource dataSource) {

		HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
		jpaVendorAdapter.setDatabase(Database.HSQL);
		jpaVendorAdapter.setShowSql(true);

		Properties hibProps = new Properties();
		hibProps.setProperty("hibernate.format_sql", "true");

		LocalContainerEntityManagerFactoryBean fb = new LocalContainerEntityManagerFactoryBean();
		fb.setPackagesToScan(packagesToScan);
		fb.setDataSource(dataSource);
		fb.setJpaVendorAdapter(jpaVendorAdapter);
		fb.setJpaProperties(hibProps);
		return fb;
	}

}
